#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/display/drm.h"
#include "lv_drivers/indev/evdev.h"
#include <unistd.h>
#include <sys/time.h>
#include <stdio.h>
#include <mpv/client.h>
#include <alsa/asoundlib.h>
#include <linux/input.h>
#include "pthread.h"

pthread_mutex_t mutex;
lv_ft_info_t font_alipuhui;
pthread_t m_media_player_thread;
pthread_t m_key_ev_thread;

mpv_handle * mpv_ctx;
lv_obj_t * bar1;
lv_obj_t * label_dura;
lv_obj_t * label_title;
lv_obj_t * label_state;

#define DISP_BUF_SIZE (320 * 240)
#define EVDEV_FILE "/dev/input/event0"

static char * alsa_mix_dev            = "default";
static char * alsa_mix_headphone_ctrl = "Headphone";

void * key_ev_thread(void * arg)
{
    int ret;
    struct input_event key_evt;

    int fd = open(EVDEV_FILE, O_RDWR);
    if(fd == -1) {
        exit(-1);
    }
    printf("open evdev successed!fd = %d\n", fd);

    while(1) {
        // 读取键值
        ret = read(fd, &key_evt, sizeof(key_evt));
        if(ret < 0) {
            perror("read failed");
            break;
        }

        if(key_evt.type == EV_KEY) {
            switch(key_evt.code) {
                case KEY_OK: {
                    //                    printf("KEY_OK %s \n", key_evt.value ? "pressed" : "released");
                    break;
                }
                case KEY_SELECT: {
                    //                    printf("KEY_SELECT %s \n", key_evt.value ? "pressed" : "released");
                    if(key_evt.value) {
                        const char * args[] = {"seek", "0", "absolute", NULL};
                        mpv_command(mpv_ctx, args);
                    }
                    break;
                }
                case KEY_VOLUMEUP: {
                    if(mpv_ctx) {
                        if(key_evt.value) {
                            int f = 0;
                            mpv_get_property(mpv_ctx, "pause", MPV_FORMAT_FLAG, (void *)&f);
                            f = !f;
                            mpv_set_property(mpv_ctx, "pause", MPV_FORMAT_FLAG, &f);
                        }
                    }
                    //                    printf("KEY_VOLUMEUP %s \n", key_evt.value ? "pressed" : "released");
                    break;
                }
                case KEY_VOLUMEDOWN: {
                    //                    printf("KEY_VOLUMEDOWN %s \n", key_evt.value ? "pressed" : "released");
                    if(key_evt.value) {
                        const char * args[] = {"seek", "+3", NULL};
                        mpv_command(mpv_ctx, args);
                    }
                    break;
                }
                default: {
                    break;
                }
            }
        }
        usleep(10000);
    }

    close(fd);
    return NULL;
}

void volume_init(void)
{
    int alsa_mix_index                         = 0;
    snd_mixer_selem_id_t * alsa_mix_sid        = NULL;
    snd_mixer_t * alsa_mix_headphone_handle    = NULL;
    snd_mixer_elem_t * alsa_mix_headphone_elem = NULL;

    long min = 0, max = 0, curr = 0;

    snd_mixer_selem_id_alloca(&alsa_mix_sid);
    snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index);
    snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_headphone_ctrl);

    if(snd_mixer_open(&alsa_mix_headphone_handle, 0) < 0) {
        printf("error 1\n");
    }
    if(snd_mixer_attach(alsa_mix_headphone_handle, alsa_mix_dev) < 0) {
        printf("error 2\n");
    }

    snd_mixer_selem_register(alsa_mix_headphone_handle, NULL, NULL);

    if(snd_mixer_load(alsa_mix_headphone_handle) < 0) {
        printf("error 3\n");
    }

    alsa_mix_headphone_elem = snd_mixer_find_selem(alsa_mix_headphone_handle, alsa_mix_sid);
    if(alsa_mix_headphone_elem == NULL) {
        printf("cannot find\n");
    }
    snd_mixer_selem_get_playback_volume_range(alsa_mix_headphone_elem, &min, &max);
    snd_mixer_selem_get_playback_volume(alsa_mix_headphone_elem, SND_MIXER_SCHN_UNKNOWN, &curr);
    printf("max: %ld,min: %ld, curr:%ld", max, min, curr);

    snd_mixer_selem_set_playback_volume_all(alsa_mix_headphone_elem, 63);
    snd_mixer_selem_set_playback_switch_all(alsa_mix_headphone_elem, 1);

    snd_mixer_close(alsa_mix_headphone_handle);
}

void * media_player_thread(void * arg)
{
    char * args    = (char *)arg;
    int64_t volume = 100;

    volume_init();
    mpv_ctx = mpv_create();
    mpv_set_option_string(mpv_ctx, "input-vo-keyboard", "no");
    mpv_set_option_string(mpv_ctx, "vo", "null");
    mpv_set_option(mpv_ctx, "volume", MPV_FORMAT_INT64, (void *)&volume);

    mpv_observe_property(mpv_ctx, 0, "time-pos", MPV_FORMAT_DOUBLE);
    mpv_observe_property(mpv_ctx, 0, "pause", MPV_FORMAT_FLAG);
    mpv_observe_property(mpv_ctx, 0, "paused-for-cache", MPV_FORMAT_FLAG);

    mpv_initialize(mpv_ctx);
    const char * cmd[] = {"loadfile", args, NULL};
    mpv_command(mpv_ctx, cmd);

    // Let it play, and wait until the user quits.
    while(1) {
        mpv_event * event         = mpv_wait_event(mpv_ctx, 10000);
        mpv_event_property * prop = (mpv_event_property *)event->data;
        //        printf("event: %s\n", mpv_event_name(event->event_id));
        switch(event->event_id) {
            case MPV_EVENT_PROPERTY_CHANGE: {
                if(strcmp(prop->name, "time-pos") == 0) // 状态栏显示当前文件的播放时间（单位：秒）
                {
                    if(prop->format == MPV_FORMAT_DOUBLE) // 属性数据的值格式是：double
                    {
                        double time = *(double *)prop->data;
                        double duration;
                        mpv_get_property(mpv_ctx, "duration", MPV_FORMAT_DOUBLE, &duration);
                        pthread_mutex_lock(&mutex);
                        lv_bar_set_value(bar1, (int)time, LV_ANIM_OFF);
                        lv_label_set_text_fmt(label_dura, "%.2fs / %.2fs", time, duration);
                        pthread_mutex_unlock(&mutex);
                    } else if(prop->format == MPV_FORMAT_NONE) // 属性数据的值格式未知 (可能意味着播放已停止)
                    {
                        pthread_mutex_lock(&mutex);
                        lv_label_set_text_fmt(label_dura, "%.2f/%.2f", 0, 0);
                        lv_bar_set_value(bar1, 0, LV_ANIM_OFF);
                        pthread_mutex_unlock(&mutex);
                    }
                } else if(strcmp(prop->name, "pause") == 0) {
                    if(prop->format == MPV_FORMAT_FLAG) {
                        if(*(bool *)prop->data) {
                            pthread_mutex_lock(&mutex);
                            lv_label_set_text_static(label_state, "暂停播放");
                            pthread_mutex_unlock(&mutex);
                        } else {
                            pthread_mutex_lock(&mutex);
                            lv_label_set_text_static(label_state, "播放中");
                            pthread_mutex_unlock(&mutex);
                        }
                    }
                } else if(strcmp(prop->name, "paused-for-cache") == 0) {
                    if(prop->format == MPV_FORMAT_FLAG) {
                        if(*(bool *)prop->data) {
                            pthread_mutex_lock(&mutex);
                            lv_label_set_text_static(label_state, "网络加载中...");
                            pthread_mutex_unlock(&mutex);
                        } else {
                            pthread_mutex_lock(&mutex);
                            lv_label_set_text_static(label_state, "播放中");
                            pthread_mutex_unlock(&mutex);
                        }
                    }
                }
                break;
            }
            case MPV_EVENT_FILE_LOADED: {
                double duration;
                mpv_get_property(mpv_ctx, "duration", MPV_FORMAT_DOUBLE, &duration);
                char * filename    = mpv_get_property_string(mpv_ctx, "media-title");
                char * file_format = mpv_get_property_string(mpv_ctx, "file-format");
                int samplerate = 0, channel_count = 0;

                // load file info
                mpv_node node;
                mpv_get_property(mpv_ctx, "audio-params", MPV_FORMAT_NODE, &node);
                if(node.format == MPV_FORMAT_NODE_MAP) {
                    for(int i = 0; i < node.u.list->num; i++) {
                        if(strcmp(node.u.list->keys[i], "samplerate") == 0) {
                            if(node.u.list->values[i].format == MPV_FORMAT_INT64)
                                samplerate = (int)node.u.list->values[i].u.int64;
                        } else if(strcmp(node.u.list->keys[i], "channel-count") == 0) {
                            if(node.u.list->values[i].format == MPV_FORMAT_INT64)
                                channel_count = (int)node.u.list->values[i].u.int64;
                        }
                    }
                }

                pthread_mutex_lock(&mutex);
                lv_bar_set_range(bar1, 0, (int)duration);
                lv_label_set_text_fmt(label_title, "正在播放: %s\n(%s %dch %dHz)", filename, file_format, channel_count,
                                      samplerate);
                pthread_mutex_unlock(&mutex);

                mpv_free(filename);
                mpv_free(file_format);
                break;
            }
            case MPV_EVENT_END_FILE: {
                pthread_mutex_lock(&mutex);
                lv_label_set_text_static(label_state, "播放完成");
                pthread_mutex_unlock(&mutex);
                break;
            }
            case MPV_EVENT_SHUTDOWN: {
                exit(0);
                break;
            }
            default: break;
        }
    }

    mpv_terminate_destroy(mpv_ctx);
    return NULL;
}

int main(int argc, char * argv[])
{
    if(argc != 2) {
        printf("pass a single media file as argument\n");
        return 1;
    }

    /*LittlevGL init*/
    lv_init();

    /*Linux frame buffer device init*/
    fbdev_init();
    pthread_mutex_init(&mutex, 0);

    /*A small buffer for LittlevGL to draw the screen's content*/
    static lv_color_t buf[DISP_BUF_SIZE];

    /*Initialize a descriptor for the buffer*/
    static lv_disp_draw_buf_t disp_buf;
    lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);

    /*Initialize and register a display driver*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.draw_buf = &disp_buf;
    disp_drv.flush_cb = fbdev_flush;
    disp_drv.hor_res  = 320;
    disp_drv.ver_res  = 240;
    lv_disp_drv_register(&disp_drv);

    //    evdev_init();
    //    static lv_indev_drv_t indev_drv_1;
    //    lv_indev_drv_init(&indev_drv_1); /*Basic initialization*/
    //    indev_drv_1.type = LV_INDEV_TYPE_POINTER;
    //    /*This function will be called periodically (by the library) to get the mouse position and state*/
    //    indev_drv_1.read_cb      = evdev_read;
    //    lv_indev_t * mouse_indev = lv_indev_drv_register(&indev_drv_1);

    font_alipuhui.name   = "/root/AlibabaPuHuiTi-3-55-Regular.ttf";
    font_alipuhui.weight = 14;
    font_alipuhui.style  = FT_FONT_STYLE_NORMAL;
    lv_ft_font_init(&font_alipuhui);
    lv_obj_remove_style_all(lv_scr_act());

    lv_obj_t * center_obj = lv_obj_create(lv_scr_act());
    lv_obj_remove_style_all(center_obj);
    lv_obj_set_layout(center_obj, LV_LAYOUT_FLEX);
    lv_obj_set_flex_flow(center_obj, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_size(center_obj, 300, 200);
    lv_obj_set_align(center_obj, LV_ALIGN_CENTER);

    bar1 = lv_bar_create(center_obj);
    //    lv_obj_set_size(bar1, 200, 10);
    lv_bar_set_value(bar1, 0, LV_ANIM_ON);

    label_dura = lv_label_create(center_obj);
    lv_obj_set_style_text_font(label_dura, font_alipuhui.font, LV_PART_MAIN);
    //    lv_obj_set_style_text_align(label_dura, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
    label_title = lv_label_create(center_obj);
    lv_obj_set_style_text_font(label_title, font_alipuhui.font, LV_PART_MAIN);
    label_state = lv_label_create(center_obj);
    lv_obj_set_style_text_font(label_state, font_alipuhui.font, LV_PART_MAIN);

    pthread_create(&m_media_player_thread, NULL, media_player_thread, argv[1]);
    pthread_create(&m_key_ev_thread, NULL, key_ev_thread, NULL);

    while(1) {
        pthread_mutex_lock(&mutex);
        lv_timer_handler();
        pthread_mutex_unlock(&mutex);
        usleep(5000);
    }

    return 0;
}

/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
    static uint64_t start_ms = 0;
    if(start_ms == 0) {
        struct timeval tv_start;
        gettimeofday(&tv_start, NULL);
        start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
    }

    struct timeval tv_now;
    gettimeofday(&tv_now, NULL);
    uint64_t now_ms;
    now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;

    uint32_t time_ms = now_ms - start_ms;
    return time_ms;
}
